package pers.dawnyang.config.aspect;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import com.alibaba.fastjson.JSON;
import lombok.extern.log4j.Log4j2;
import pers.dawnyang.common.annotation.OperLog;
import pers.dawnyang.common.domain.entity.SysOperLog;
import pers.dawnyang.common.util.web.IPUtil;
import pers.dawnyang.modular.system.service.OperLogService;

/**
 * 记录操作日志（配合注解@OperLog），和错误日志
 * 
 * @author dawn yang 2020年8月25日
 *         下午4:12:42
 *
 */
@Aspect
@Order(2)
@Component
@Log4j2
public class OperLogAspect {

  @Autowired
  private OperLogService operLogService;

  /**
   * 设置操作日志切入点 记录操作日志 在注解的位置切入代码
   */
  @Pointcut("@annotation(pers.dawnyang.common.annotation.OperLog)")
  // @Pointcut("execution(*
  // pers.dawnyang.modular.*.controller..*.*(..))
  // && !execution(*
  // pers.dawnyang.modular.*.controller.ErrorController.*(..))")
  public void operLogPoinCut() {}

  /**
   * 设置操作异常切入点记录异常日志 扫描所有controller包下操作
   */
  @Pointcut("execution(* pers.dawnyang.modular.*.controller..*.*(..))")
  public void operExceptionLogPoinCut() {}

  /**
   * 请求完成后执行
   * 
   * @param joinPoint
   * @param res
   */
  @AfterReturning(value = "operLogPoinCut()", returning = "res")
  public void saveOperLog(JoinPoint joinPoint, Object res) {
    handle(joinPoint, null, res);
  }

  /**
   * 抛出异常时执行
   * 
   * @param joinPoint
   * @param e
   */
  @AfterThrowing(pointcut = "operExceptionLogPoinCut()", throwing = "e")
  public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
    handle(joinPoint, e, null);
  }

  protected void handle(final JoinPoint joinPoint, final Throwable e, Object res) {
    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = (HttpServletRequest) requestAttributes
        .resolveReference(RequestAttributes.REFERENCE_REQUEST);
    SysOperLog operlog = new SysOperLog();
    // 从切面织入点处通过反射机制获取织入点处的方法
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    // 获取切入点所在的方法
    Method method = signature.getMethod();
    // 获取操作
    OperLog opLog = method.getAnnotation(OperLog.class);
    if (opLog != null) {
      String operModul = opLog.operModul();
      String operType = opLog.operType().name();
      String operDesc = opLog.operDesc();
      operlog.setOperModul(operModul); // 操作模块
      operlog.setOperType(operType); // 操作类型
      operlog.setOperDesc(operDesc); // 操作描述
    }
    // 获取请求的类名
    String className = joinPoint.getTarget().getClass().getName();
    // 获取请求的方法名
    String methodName = method.getName();
    methodName = className + "." + methodName;
    operlog.setOperMethod(methodName);

    // 获取请求参数
    Object[] args = joinPoint.getArgs();
    LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
    String[] paramNames = u.getParameterNames(method);
    if (args != null && paramNames != null) {
      String params = "";
      for (int i = 0; i < args.length; i++) {
        params += "  " + paramNames[i] + ": " + args[i];
      }
      operlog.setOperReqParam(params);
    }

    operlog.setOperResParam(JSON.toJSONString(res));
    Subject subject = SecurityUtils.getSubject();
    String userCode = (String) subject.getPrincipal();
    operlog.setOperUserCode(userCode);
    operlog.setOperIp(IPUtil.getIpAddr(request));
    operlog.setOperUri(request.getRequestURI());
    operlog.setOperTime(new Date());

    if (null != e) {
      String error = stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace());
      operlog.setErrorMsg(error);
      operlog.setOperStatus(0);
    } else {
      operlog.setOperStatus(1);
    }
    operLogService.insertOperLog(operlog);
  }

  /**
   * 转换request 请求参数
   *
   * @param paramMap request获取的参数数组
   */
  public Map<String, String> converMap(Map<String, String[]> paramMap) {
    Map<String, String> rtnMap = new HashMap<String, String>();
    for (String key : paramMap.keySet()) {
      rtnMap.put(key, paramMap.get(key)[0]);
    }
    return rtnMap;
  }

  /**
   * 转换异常信息为字符串
   *
   * @param exceptionName 异常名称
   * @param exceptionMessage 异常信息
   * @param elements 堆栈信息
   */
  public String stackTraceToString(String exceptionName, String exceptionMessage,
      StackTraceElement[] elements) {
    StringBuffer strbuff = new StringBuffer();
    for (StackTraceElement stet : elements) {
      strbuff.append(stet + "\n");
    }
    String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
    return message;
  }
}
